﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking.BackgroundTransfer;
using Windows.Storage;
using Windows.Web;
using ZipHelperWinRT;

namespace SharPy.WebObjects
{
    /// <summary>
    /// Class to handle downloads and unzips
    /// </summary>    
    class clDownloadHelper
    {

        private List<DownloadOperation> activeDownloads;
        private CancellationTokenSource cts;

        /// <summary>
        /// Event to send info about current download state
        /// </summary>
        public event EventHandler<DownloaderStatus> SetStatus;

        protected virtual void OnSetStatus(DownloaderStatus pStatus)
        {
            EventHandler<DownloaderStatus> handler = SetStatus;

            if (handler != null)
            {
                handler(this, pStatus);
            }
        }

        public clDownloadHelper() 
        {
            // Re-create the CancellationTokenSource and activeDownloads for future downloads.
            cts = new CancellationTokenSource();
            activeDownloads = new List<DownloadOperation>();
        }

        internal async Task<StorageFile> StartDownload(bool requestUnconstrainedDownload, string pUrl, string pDesiredName, bool needToUnzip = true, BackgroundTransferPriority priority = BackgroundTransferPriority.Default)
        {
            // The URI is validated by calling Uri.TryCreate() that will return 'false' for strings that are not valid URIs.
            // Note that when enabling the text box users may provide URIs to machines on the intrAnet that require
            // the "Home or Work Networking" capability.
            Uri source;
            if (!Uri.TryCreate(pUrl, UriKind.Absolute, out source))
            {
                Windows.UI.Popups.MessageDialog md5 = new Windows.UI.Popups.MessageDialog("Invalid URL");
                await md5.ShowAsync();
                return null;
            }

            StorageFolder destinationFolder = await ApplicationData.Current.RoamingFolder.CreateFolderAsync("Downloads", CreationCollisionOption.OpenIfExists);

            String localFileName = pDesiredName;

            String ext = System.IO.Path.GetExtension(localFileName);
            if (!String.Equals(ext, ".zip", StringComparison.OrdinalIgnoreCase) && needToUnzip)
            {
                Windows.UI.Popups.MessageDialog md6 = new Windows.UI.Popups.MessageDialog("Invalid file type. Please make sure the file type is zip.");
                await md6.ShowAsync();
                return null;
            }

            try
            {
                StorageFile localFile = await destinationFolder.CreateFileAsync(localFileName, CreationCollisionOption.ReplaceExisting);

                BackgroundDownloader downloader = new BackgroundDownloader();
                DownloadOperation download = downloader.CreateDownload(source, localFile);

                OnSetStatus(new DownloaderStatus("Downloading file", pDesiredName));

                download.Priority = priority;

                // In this sample, we do not show how to request unconstrained download.
                // For more information about background transfer, please refer to the SDK Background transfer sample:
                // http://code.msdn.microsoft.com/windowsapps/Background-Transfer-Sample-d7833f61
                if (!requestUnconstrainedDownload)
                {
                    // Attach progress and completion handlers.
                    await HandleDownloadAsync(download, true);

                    if (needToUnzip)
                    {
                        StorageFolder unzipFolder =
                        await destinationFolder.CreateFolderAsync(System.IO.Path.GetFileNameWithoutExtension(localFile.Name),
                        CreationCollisionOption.ReplaceExisting);

                        await UnZipFileAsync(localFile, unzipFolder);
                    }
                    return localFile;
                }
            }
            catch (Exception ex)
            {
                OnSetStatus(new DownloaderStatus(ex.Message, "error"));
            }

            return null;
        }

        private async Task HandleDownloadAsync(DownloadOperation download, bool start)
        {
            try
            {
                OnSetStatus(new DownloaderStatus("Running: " + download.Guid, "Status"));

                // Store the download so we can pause/resume.
                activeDownloads.Add(download);

                Progress<DownloadOperation> progressCallback = new Progress<DownloadOperation>(DownloadProgress);
                if (start)
                {
                    // Start the download and attach a progress handler.
                    await download.StartAsync().AsTask(cts.Token, progressCallback);
                }
                else
                {
                    // The download was already running when the application started, re-attach the progress handler.
                    await download.AttachAsync().AsTask(cts.Token, progressCallback);
                }

                ResponseInformation response = download.GetResponseInformation();

                OnSetStatus(new DownloaderStatus(String.Format(CultureInfo.CurrentCulture, "Completed: {0}, Status Code: {1}",
                    download.Guid, response.StatusCode), "Status"));
            }
            catch (TaskCanceledException)
            {
                OnSetStatus(new DownloaderStatus("Canceled: " + download.Guid, "Status"));
            }
            catch (Exception ex)
            {
                if (!IsExceptionHandled("Execution error", ex, download))
                {
                    throw;
                }
            }
            finally
            {
                activeDownloads.Remove(download);
            }
        }

        private bool IsExceptionHandled(string title, Exception ex, DownloadOperation download = null)
        {
            WebErrorStatus error = BackgroundTransferError.GetStatus(ex.HResult);
            if (error == WebErrorStatus.Unknown)
            {
                return false;
            }

            if (download == null)
            {
                OnSetStatus(new DownloaderStatus(String.Format(CultureInfo.CurrentCulture, "Error: {0}: {1}", title, error),
                    "error"));
            }
            else
            {
                OnSetStatus(new DownloaderStatus(String.Format(CultureInfo.CurrentCulture, "Error: {0} - {1}: {2}", download.Guid, title,
                    error), "error"));
            }

            return true;
        }

        // Note that this event is invoked on a background thread, so we cannot access the UI directly.
        private void DownloadProgress(DownloadOperation download)
        {
            MarshalLog(String.Format(CultureInfo.CurrentCulture, "Progress: {0}, Status: {1}", download.Guid,
                download.Progress.Status));

            double percent = 100;
            if (download.Progress.TotalBytesToReceive > 0)
            {
                percent = download.Progress.BytesReceived * 100 / download.Progress.TotalBytesToReceive;
            }

            MarshalLog(String.Format(CultureInfo.CurrentCulture, " - Transfered bytes: {0} of {1}, {2}%",
                download.Progress.BytesReceived, download.Progress.TotalBytesToReceive, percent));

            if (download.Progress.HasRestarted)
            {
                MarshalLog(" - Download restarted");
            }

            if (download.Progress.HasResponseChanged)
            {
                // We've received new response headers from the server.
                MarshalLog(" - Response updated; Header count: " + download.GetResponseInformation().Headers.Count);

                // If you want to stream the response data this is a good time to start.
                // download.GetResultStreamAt(0);
            }
        }

        private void MarshalLog(string value)
        {
            OnSetStatus(new DownloaderStatus(value, "log"));
            //var ignore = this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            //{
            //    OnSetStatus(new DownloaderStatus(value, "log"));
            //});
        }
       
        private async Task UnZipFileAsync(StorageFile zipFile, StorageFolder unzipFolder)
        {
            try
            {
                OnSetStatus(new DownloaderStatus("Unziping file: " + zipFile.DisplayName + "...", "Status"));
                await ZipHelper.UnZipFileAsync(zipFile, unzipFolder);
                OnSetStatus(new DownloaderStatus("Unzip file '" + zipFile.DisplayName + "' successfully!", "Status"));
            }
            catch (Exception ex)
            {
                OnSetStatus(new DownloaderStatus("Failed to unzip file ..." + ex.Message, "error"));
            }
        }
    }

    class DownloaderStatus : EventArgs
    {
        public string sTitle;
        public string sMessage;

        public DownloaderStatus() { }

        public DownloaderStatus(string pTitle, string pMessage)
        {
            this.sTitle = pTitle;
            this.sMessage = pMessage;
        }
    }
}
